Terraform でパスと IP を指定して AWS WAF のルールグループを動的に生成する
コーヒーが好きな emi です。
ALB に紐づける AWS WAF のルールグループを動的に生成する Terraform コードを作成したので紹介します。
このコードの使い方は 使い方 、注意 を先に参照ください。
- 想定読者
- 1 つの
.tf
ファイルで VPC や EC2 を apply するまでを実施したことがある - モジュールの概念を知っている
- AWS WAF を構築したことがあり、Web ACL、ルールグループ、ルールなどの概念を知っている
- 1 つの
追記:2023/3/30、Terraform 公式からスタイルガイドが公開されました。本記事で案内するコード例とは方針が異なっている部分もあります。コーディングの際は公式スタイルガイドやそれぞれのプロジェクトの方針に従ってください。
やることのイメージ
- Terraform コードの中で、名前、優先度、パス、IP の組み合わせを追記
- Terraform で更新
- 指定した内容で IP Sets とルールグループを動的に生成
「指定したパスへのアクセス」かつ「指定した IP アドレスからのアクセス」以外はブロックする、というルールグループを動的に生成します。
具体的に値を入れたときのイメージは以下です。
ディレクトリ構造
Cloud9 で「terraform-test2」という名前の Terraform 実行環境を作成しています。環境の準備方法は以下ブログを参照ください。
/home/ec2-user/environment
配下のディレクトリ構造は以下のようになっています。
test-terraform ├envs | └dev-waf | ├backend.tf | ├main.tf │ └provider.tf ├modules | └waf │ ├locals.tf │ ├main.tf │ ├outputs.tf │ └variables.tf └shared └common_values ├outputs.tf └waf_rules.tf
それぞれ以下のように呼びます。
- envs 配下:ルートモジュール
- modules 配下:子モジュール
- shared 配下:共有モジュール
モジュールに関する補足(クリックで展開)
ルートモジュール(envs)
ルートモジュールは Terraform 設定の最上位レベルを表し、インフラストラクチャ全体の構成を定義する場所です。今回は子モジュールである modules.waf
を呼び出して AWS アカウント上に WAF リソースを作成します。
子モジュール(modules)
子モジュールは再利用可能な Terraform 設定の単位を表し、特定の機能や構成を実装するために使用されます。今回は WAF モジュール 1 つしか用意しませんが、例えば VPC モジュール、container モジュールなどを用意しておくことで、複数の環境に同じモジュールを使っていくつも同じ設定の VPC やコンテナを作成することができます。
共有モジュール(shared/common_values)
共有モジュールは複数の環境や設定で共有される共通の値を保持するために使用されます。例えば dev 環境、stg 環境、prd 環境の 3 環境ある場合、この 3 つの環境に共通の値をここで定義することで、設定箇所が一つになります。
shared/common_values
waf_rules.tf
ここに追加したいルールグループの要素を追記します。
shared/common_values/waf_rules.tf(クリックで展開)
locals { # アクセス制限をかける path と IP アドレスのリスト access_ristricted_paths_ips = [ # { # name = "access-ristrict-sample1" # priority = 1 # <- 例。 priority は 1~999 で設定する。WAF モジュール側で +1000 # allow_path = "/ristrict/sample1/" # allow_ip_list = [ # "xx.xx.xx.xx/32", // AWS WAF の制約で末尾 /0 は指定できない(0.0.0.0/0 は指定できない)。すべてのアクセスを許可する場合、["0.0.0.0/1","128.0.0.0/1"] とする # "yy.yy.yy.yy/32", # "zz.zz.zz.zz/32", # ] # }, { name = "access-ristrict-sample2" priority = 1 allow_path = "/ristrict/sample2/" allow_ip_list = [ "0.0.0.0/1", "128.0.0.0/1", ] }, { name = "access-ristrict-sample3" priority = 2 allow_path = "/ristrict/sample3/" allow_ip_list = [ "0.0.0.0/1", "128.0.0.0/1", ] }, ] }
outputs.tf
shared/common_values/outputs.tf(クリックで展開)
# waf_rules output "access_ristricted_paths_ips" { value = local.access_ristricted_paths_ips } # value フィールドに指定したデータの型(list, map など)はそのまま出力に反映される。 # local.access_ristricted_paths_ips が list 型で定義されていれば出力も list 型。 # output で以下のようなリストが出力される。 # access_ristricted_paths_ips = [ # { # name = "access-ristrict-sample2" # priority = 1 # allow_path = "/ristrict/sample2/" # allow_ip_list = [ # "0.0.0.0/1", # "128.0.0.0/1", # ] # }, # { # name = "access-ristrict-sample3" # priority = 2 # allow_path = "/ristrict/sample3/" # allow_ip_list = [ # "0.0.0.0/1", # "128.0.0.0/1", # ] # }, # ]
modules/waf
variables.tf
variables.tf
は Terraform モジュールの入力値(変数)を定義するファイルです。モジュールを使用する側(呼び出し元)から値を受け取り、モジュール内で使用することができます。
modules/waf/variables.tf(クリックで展開)
variable "access_ristricted_paths_ips" { type = list(object({ name = string priority = number allow_path = string allow_ip_list = list(string) })) default = [] description = "アクセス拒否パスとIPリスト" } variable "alb_arn" { type = string description = "ALB の ARN 設定情報" }
access_ristricted_paths_ips
変数type
はlist(object({...}))
と定義しました。これで、オブジェクトのリストを受け取ります。各オブジェクトにはname
、priority
、allow_path
、allow_ip_list
というフィールドがあります。default
は空のリスト[]
を設定しました。呼び出し元がこの変数に値を渡さない場合、デフォルト値として空のリストが使用されます。
alb_arn
変数type
は文字列値を受け取るようstring
で定義しました。
locals.tf
locals.tf
はモジュール内で使用するローカル変数を定義するためのファイルです。ローカル変数はモジュール内で繰り返し使用される値や、複雑な式の結果を保持するために使用されます。ローカル変数は locals
ブロックを使用して定義され、名前と値を指定します。
modules/waf/locals.tf(クリックで展開)
locals{ access_ristricted_paths_ips = { for paths_ips in var.access_ristricted_paths_ips : paths_ips.name => paths_ips } } # for で paths_ips を繰り返し処理する。 # 1 回目の繰り返し処理で var.access_ristricted_paths_ips から # shared/common_values/waf_rules.tf で定義し # shared/common_values/outputs.tf で出力した # 以下のような list(paths_ips)を得る。 # { # name = "access-ristrict-sample2" # priority = 1 # allow_path = "/ristrict/sample2/" # allow_ip_list = [ # "0.0.0.0/1", # "128.0.0.0/1", # ] # }, # paths_ips.name => paths_ips の指示では、 # paths_ips の name("access-ristrict-sample2")を # 新しい map のトップレベルキーとして設定し、 # この list(paths_ips)全体を、新しい map の値として使う。 # 新しい map 型ができあがると以下のようになる。 # { # "access-ristrict-sample2" = { # name = "access-ristrict-sample2" # priority = 1 # allow_path = "/ristrict/sample2/" # allow_ip_list = [ # "0.0.0.0/1", # "128.0.0.0/1", # ] # } # },
以下の処理について補足します。
{ for <ITEM> in <LIST>: <KEY> => <VALUE> }
<LIST>
:反復処理の対象となる list または map<ITEM>
:list または map の各要素を表す変数<KEY>
:新しい map のキーを指定する式。<VALUE>
:新しい map の値を指定する式。
map 型について補足します。
"キー" = { 値 }
map 型はキーと値のペアを持つデータ構造です。
- map 型は一意のキーと対応する値のペアを持ちます。
- キーは文字列で指定され、値には任意の型(文字列、数値、ブール値、リスト、他の map など)を指定できます。
- 宣言方法
- map は
{}
を使用して宣言します。 - キーは「トップレベルキー」「全体のキー」「変数名」と言ったりします。
{}
内部にもkey = value
のような形式が続きますが、{}
の中のキーは「内部のキー」「ネストされたキー」「map 型の中の子 キー」と呼んだりするようです。 - 例
"key" = { # トップレベルキー、全体のキー key1 = value1 # 内部のキー、ネストされたキー key2 = value2 key3 = ["xxx", "yyy",] }
- map は
main.tf
ではいよいよ AWS WAF のリソースを作成する main.tf
です。今回は以下のリソースを作成します。
- IP Sets(動的生成)
- カスタムルールグループ(動的生成)
- Web ACL
- WAF のログを出力する CloudWatch Logs
- WAF のログフィルタ設定
- ALB への WAF の割り当て
長いです。トグルを展開して確認ください。
modules/waf/main.tf(クリックで展開)
############################################ # IPsets ############################################ ## (動的生成). 指定パスの IP 許可ルールグループ(許可IP以外をブロック) resource "aws_wafv2_ip_set" "access_ristrict" { for_each = local.access_ristricted_paths_ips name = "${each.value.name}-ristrict-ipsets" description = "${each.value.name}-ristrict-ipsets" scope = "REGIONAL" addresses = each.value.allow_ip_list ip_address_version = "IPV4" tags = { Name = "${each.value.name}-ristrict-ipsets" } } ############################################ # カスタムルールグループ ############################################ ## (動的生成). 指定パスの IP 許可ルールグループ(許可IP以外をブロック) resource "aws_wafv2_rule_group" "access_ristrict" { for_each = local.access_ristricted_paths_ips name = "${each.value.name}-ristrict-waf-rulegp" description = "${each.value.name}-ristrict-waf-rulegp" scope = "REGIONAL" capacity = 3 # 1 つのルールグループに 1 つのルールのみ設定する visibility_config { cloudwatch_metrics_enabled = true # CloudWatch Metrics を無効にすると CloudWatch で確認できなくなるため、true で固定 metric_name = "${each.value.name}-ristrict-rulegp" sampled_requests_enabled = true # Web ACL ルールに一致するリクエストを表示する。true で固定 } rule { name = "${each.value.name}-ristrict-rule" priority = 1 action { block {} } statement { and_statement { statement { byte_match_statement { field_to_match { uri_path {} } positional_constraint = "STARTS_WITH" search_string = each.value.allow_path text_transformation { priority = 0 type = "NONE" } } } statement { not_statement { statement { ip_set_reference_statement { arn = aws_wafv2_ip_set.access_ristrict[each.key].arn # IPsets の ARN を指定 } } } } } } visibility_config { cloudwatch_metrics_enabled = true metric_name = "${each.value.name}-ristrict-rule" sampled_requests_enabled = true } } } ############################################ # Web ACL ############################################ resource "aws_wafv2_web_acl" "web_acl" { name = "alb-webacl" description = "alb-wabacl" scope = "REGIONAL" tags = { Name = "alb-webacl" } visibility_config { cloudwatch_metrics_enabled = true metric_name = "alb-webacl" sampled_requests_enabled = true } default_action { allow {} } ## (動的生成). 指定パスの IP 許可ルールグループ(許可IP以外をブロック) dynamic "rule" { for_each = local.access_ristricted_paths_ips content { name = "${title(rule.value.name)}PathIpRestriction" priority = rule.value.priority + 1000 override_action { none {} } statement { rule_group_reference_statement { arn = aws_wafv2_rule_group.access_ristrict[rule.key].arn } } visibility_config { cloudwatch_metrics_enabled = true metric_name = "${title(rule.value.name)}PathIpRestriction" sampled_requests_enabled = true } } } } ############################################ # WAF のログを出力する CloudWatch Logs ############################################ resource "aws_cloudwatch_log_group" "cwlogs" { name = "aws-waf-logs-${aws_wafv2_web_acl.web_acl.name}" retention_in_days = 30 tags = { Name = "aws-waf-logs-${aws_wafv2_web_acl.web_acl.name}" } } ############################################ # WAF のログフィルタ設定 ############################################ resource "aws_wafv2_web_acl_logging_configuration" "waflog" { resource_arn = aws_wafv2_web_acl.web_acl.arn log_destination_configs = [ # CloudWatch Logs aws_cloudwatch_log_group.cwlogs.arn, ] logging_filter { # デフォルトではログを記録しない default_behavior = "DROP" filter { behavior = "KEEP" # MEETS_ALL: AND条件、 MEETS_ANY: OR条件 requirement = "MEETS_ANY" ## action_condition ブロックに CAPTCHA の記載はないが apply はできる模様 https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_web_acl_logging_configuration#action-condition condition { action_condition { # CAPTCHAアクションのログを記録 action = "CAPTCHA" } } condition { action_condition { # COUNTアクションのログを記録 action = "COUNT" } } condition { action_condition { # BLOCKアクションのログを記録 action = "BLOCK" } } } } depends_on = [ aws_cloudwatch_log_group.cwlogs, ] } ############################################ # ALB への WAF の割り当て ############################################ resource "aws_wafv2_web_acl_association" "waf_association" { resource_arn = var.alb_arn #ALBのARN web_acl_arn = aws_wafv2_web_acl.web_acl.arn }
ここからそれぞれのリソースについて解説します。
1. IP Sets(動的生成)
############################################ # IP Sets(動的生成) ############################################ ## (動的生成). 指定パスの IP 許可ルールグループ(許可IP以外をブロック) resource "aws_wafv2_ip_set" "access_ristrict" { for_each = local.access_ristricted_paths_ips name = "${each.value.name}-ristrict-ipsets" description = "${each.value.name}-ristrict-ipsets" scope = "REGIONAL" addresses = each.value.allow_ip_list ip_address_version = "IPV4" tags = { Name = "${each.value.name}-ristrict-ipsets" } }
- 6 行目の
for_each
で繰り返し処理を書いています。local.access_ristricted_paths_ips
から以下のような map が複数回読み込まれます。"access-ristrict-sample2" = { name = "access-ristrict-sample2" priority = 1 allow_path = "/ristrict/sample2/" allow_ip_list = [ "0.0.0.0/1", "128.0.0.0/1", ] }
"access-ristrict-sample3" = { name = "access-ristrict-sample3" priority = 2 allow_path = "/ristrict/sample3/" allow_ip_list = [ "0.0.0.0/1", "128.0.0.0/1", ] }
:
: each.value.name
ではlocal.access_ristricted_paths_ips
で繰り返し読み込まれた map の中のname
オブジェクトの値(ここではaccess-ristrict-sample2
やaccess-ristrict-sample3
)が繰り返し入ってきます。name = "${each.value.name}-ristrict-ipsets"
では、1 回目の繰り返しで"access-ristrict-sample2-ristrict-ipsets"
という値になります。
- 同様に
each.value.allow_ip_list
には["0.0.0.0/1", "128.0.0.0/1",]
という IP のリストが入ります。 - 繰り返し生成された IP Sets の ARN や ID は以下のような map で保持されることになります。
aws_wafv2_ip_set.access_ristrict = { "access-ristrict-sample2" = { arn = "xxxxx" id = "xxxxx" }, "access-ristrict-sample3" = { arn = "yyyyy" id = "xxxxx" }, }
2. カスタムルールグループ(動的生成)
作成するカスタムルールグループのデフォルトアクションは block です。
カスタムルールグループも IP Sets 同様に動的に生成するので for_each = local.access_ristricted_paths_ips
で繰り返し値を読み込んでいます。
ルールのステートメント部分を抜き出して補足します。
statement { and_statement { statement { byte_match_statement { field_to_match { uri_path {} } positional_constraint = "STARTS_WITH" search_string = each.value.allow_path text_transformation { priority = 0 type = "NONE" } } } statement { not_statement { statement { ip_set_reference_statement { arn = aws_wafv2_ip_set.access_ristrict[each.key].arn # IPsets の ARN を指定 } } } } } }
- 3-15 行目の statement と 17-25 行目の statement を
and_statement
(AND 条件)でつなげています。 - 3-15 行目の statement
each.value.allow_path
で始まる URI から始まる path へのアクセスである、という条件です。each.value.allow_path
にはlocal.access_ristricted_paths_ips
で読み込まれた map の中のallow_path
オブジェクトの値が入ります。- 1 回目の繰り返しでは
/ristrict/sample2/
が入ります。
- 1 回目の繰り返しでは
- 17-25 行目の statement
ip_set_reference_statement {}
で指定した IP「ではない」IP アドレスからのアクセスである、という条件です。arn = aws_wafv2_ip_set.access_ristrict[each.key].arn
で、動的に生成した IP Sets の ARN を順番に読み込みます。[each.key]
にはlocal.access_ristricted_paths_ips
で読み込まれた map の中 key(トップレベルキー)が入ります。- 1 回目の繰り返しでは
arn = aws_wafv2_ip_set.access_ristrict[access-ristrict-sample2].arn
となります。つまり、1 回目の繰り返しで読み込まれた IP Sets の ARN がここに入ってくるというわけです。
- 繰り返し生成されたカスタムルールグループの ARN や ID は以下のような map で保持されることになります。
aws_wafv2_rule_group.access_ristrict = { "access-ristrict-sample2" = { arn = "xxxxx" id = "xxxxx" }, "access-ristrict-sample3" = { arn = "yyyyy" id = "xxxxx" }, }
Web ACL
今回は AWS WAF を ALB に紐づけるので、スコープは "REGIONAL"
としています。Web ACL のデフォルトアクションは allow です。
ルールブロックを動的生成する部分を抜き出して解説します。
## (動的生成). 指定パスの IP 許可ルールグループ(許可IP以外をブロック) dynamic "rule" { for_each = local.access_ristricted_paths_ips content { name = "${title(rule.value.name)}PathIpRestriction" priority = rule.value.priority + 1000 override_action { none {} } statement { rule_group_reference_statement { arn = aws_wafv2_rule_group.access_ristrict[rule.key].arn } } visibility_config { cloudwatch_metrics_enabled = true metric_name = "${title(rule.value.name)}PathIpRestriction" sampled_requests_enabled = true } } }
dynamic
は Terraform のブロック({}
で囲まれたかたまり)を動的に生成する際に使用します。for_each
で繰り返す回数や条件を指定します。content {}
で繰り返し構造の中身の要素を定義します。dynamic "xxx" { for_each = yyy # 繰り返す回数や条件を指定 content { # 繰り返す内容 } }
for_each = local.access_ristricted_paths_ips
で map を繰り返し読み込みます。rule.value.name
dynamic
ブロックによってrule
ブロックが生成されます。rule.value
はfor_each
で指定された map のオブジェクトの現在の要素の値を表します。- 1 回目の繰り返しで
rule.value.name
は、現在の要素のname
属性の値(access-ristrict-sample2
)を参照します。
name = "${title(rule.value.name)}PathIpRestriction"
では title 関数を使用して、文字列をキャメルケースに変換しています。- title - Functions - Configuration Language | Terraform | HashiCorp Developer
- 1 回目の繰り返しでは
name = "${title(access-ristrict-sample2)}PathIpRestriction"
→name = "Access-Ristrict-Sample2PathIpRestriction"
となるわけです。
arn = aws_wafv2_rule_group.access_ristrict[rule.key].arn
で、動的に生成したカスタムルールグループの ARN を順番に読み込みます。[rule.key]
にはdynamic
ブロックで生成されたrule
ブロック内でfor_each
で読み込まれた map の key(トップレベルキー)が入ります。- 1 回目の繰り返しでは
arn = aws_wafv2_rule_group.access_ristrict[access-ristrict-sample2].arn
となります。
WAF のログを出力する CloudWatch Logs
############################################ # WAF のログを出力する CloudWatch Logs ############################################ resource "aws_cloudwatch_log_group" "cwlogs" { name = "aws-waf-logs-${aws_wafv2_web_acl.web_acl.name}" retention_in_days = 30 tags = { Name = "aws-waf-logs-${aws_wafv2_web_acl.web_acl.name}" } }
名前と CloudWatch Logs の保持期間 retention_in_days
を 30 日間に設定しています。WAF のログを保存する CloudWatch Logs の名前は aws-waf-logs-
で始まる必要がありますので注意してください。
ロググループの命名 - AWS WAF、 AWS Firewall Manager、および AWS Shield Advanced
WAF のログフィルタ設定
WAF のログフィルタを設定しています。フィルタ部分のみ抜き出します。
logging_filter { # デフォルトではログを記録しない default_behavior = "DROP" filter { behavior = "KEEP" # MEETS_ALL: AND条件、 MEETS_ANY: OR条件 requirement = "MEETS_ANY" ## action_condition ブロックに CAPTCHA の記載はないが apply はできる模様 https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/wafv2_web_acl_logging_configuration#action-condition condition { action_condition { # CAPTCHAアクションのログを記録 action = "CAPTCHA" } } condition { action_condition { # COUNTアクションのログを記録 action = "COUNT" } } condition { action_condition { # BLOCKアクションのログを記録 action = "BLOCK" } } } } depends_on = [ aws_cloudwatch_log_group.cwlogs, ]
デフォルトではログを取得せず、CAPTCHA
、COUNT
、COUNT
アクションのログのみ取得するようにしています。
depends_on
は Terraform のメタ引数の一つで、リソース間の依存関係を明示的に指定するために使用します。今回は aws_cloudwatch_log_group.cwlogs
リソースが作成される前に aws_wafv2_web_acl_logging_configuration.waflog
リソースが作成されることを防いでいます。
The depends_on Meta-Argument - Configuration Language | Terraform | HashiCorp Developer
ALB への WAF の割り当て
############################################ # ALB への WAF の割り当て ############################################ resource "aws_wafv2_web_acl_association" "waf_association" { resource_arn = var.alb_arn #ALBのARN web_acl_arn = aws_wafv2_web_acl.web_acl.arn }
Web ACL の ARN と ALB の ARN を指定して関連付けをおこなっています。ALB の ARN はこの後ルートモジュール側で渡します。
outputs.tf
modules/waf/outputs.tf(クリックで展開)
################################################## ## CloudWatch Logs ################################################## # CloudWatch Logs Group ARN output "cloudwatch_log_group_arn" { value = aws_cloudwatch_log_group.cwlogs.arn description = "The ARN of the CloudWatch Log Group for AWS WAF logs." } ################################################## ## Web ACL ################################################## # Web ACL ARN output "web_acl_arn" { value = aws_wafv2_web_acl.web_acl.arn description = "The ARN of the Web ACL." } # Web ACL ID output "web_acl_id" { value = aws_wafv2_web_acl.web_acl.id description = "The ID of the Web ACL." } # Web ACL name output "web_acl_name" { description = "The name of the WAFv2 WebACL." value = aws_wafv2_web_acl.web_acl.name } # web acl capacity output "web_acl_capacity" { description = "The web ACL capacity units (WCUs) currently being used by the WAFv2 WebACL." value = aws_wafv2_web_acl.web_acl.capacity } # Web ACL Logging Configuration # 通常 Web ACL自体のARNと同じだが、ログ設定のコンテキストでの使用に便利 output "web_acl_logging_configuration_resource_arn" { value = aws_wafv2_web_acl_logging_configuration.waflog.resource_arn description = "The resource ARN for the Web ACL Logging Configuration." } ################################################## ## IP Sets ################################################## output "ip_set" { value = aws_wafv2_ip_set.access_ristrict description = "WAFv2 IP Sets created for access restriction." } ################################################## ## Rule Group ################################################## output "rule_group" { value = aws_wafv2_rule_group.access_ristrict description = "WAFv2 Rule Groups created for access restriction." }
IP Sets とルールグループは動的に生成されます。
for_each
で繰り返し作成されるリソースは map 型で保持されるので、output
では map に保持された複数リソースが返ってきます。
ちなみに count
で複数作成されるリソースは list 型で保持されます。
追記:以前は私が上記仕様を理解していなかったため、for 文で繰り返し output していました。問題なく apply はできたのですが、上記の書き方の方がシンプルであるため修正しました。
参考:修正前のコード
modules/waf/outputs.tf(クリックで展開)
################################################## ## CloudWatch Logs ################################################## # CloudWatch Logs Group ARN output "cloudwatch_log_group_arn" { value = aws_cloudwatch_log_group.cwlogs.arn description = "The ARN of the CloudWatch Log Group for AWS WAF logs." } ################################################## ## Web ACL ################################################## # Web ACL ARN output "web_acl_arn" { value = aws_wafv2_web_acl.web_acl.arn description = "The ARN of the Web ACL." } # Web ACL ID output "web_acl_id" { value = aws_wafv2_web_acl.web_acl.id description = "The ID of the Web ACL." } # Web ACL name output "web_acl_name" { description = "The name of the WAFv2 WebACL." value = aws_wafv2_web_acl.web_acl.name } # web acl capacity output "web_acl_capacity" { description = "The web ACL capacity units (WCUs) currently being used by the WAFv2 WebACL." value = aws_wafv2_web_acl.web_acl.capacity } # Web ACL Logging Configuration # 通常 Web ACL自体のARNと同じだが、ログ設定のコンテキストでの使用に便利 output "web_acl_logging_configuration_resource_arn" { value = aws_wafv2_web_acl_logging_configuration.waflog.resource_arn description = "The resource ARN for the Web ACL Logging Configuration." } ################################################## ## IP Sets ################################################## # IP Sets の作成は動的であるため、それぞれの IP Set の ARN を map 型で出力 output "ip_set_arns" { value = { for k, v in aws_wafv2_ip_set.access_ristrict : k => v.arn } description = "The ARNs of the WAFv2 IP Sets created for access restriction." } # IP Sets の作成は動的であるため、それぞれの IP Set の ID を map 型で出力 output "ip_set_ids" { value = { for k, v in aws_wafv2_ip_set.access_ristrict : k => v.id } description = "The IDs of the WAFv2 IP Sets created for access restriction." } ################################################## ## Rule Group ################################################## # Rule Group の作成は動的であるため、それぞれの Rule Group の ARN を map 型で出力 output "rule_group_arns" { description = "The ARNs of the WAFv2 Rule Groups created for access restriction." value = { for k, v in aws_wafv2_rule_group.access_ristrict : k => v.arn } } # Rule Group の作成は動的であるため、それぞれの Rule Group の ID を map 型で出力 output "rule_group_ids" { description = "The IDs of the WAFv2 Rule Groups created for access restriction." value = { for k, v in aws_wafv2_rule_group.access_ristrict : k => v.id } } # k は "key" の頭文字、map の key(トップレベルキー)を表すために使用 # v は "value" の頭文字、map 内の値を表すために使用
以下の処理について補足します。
for k, v in aws_wafv2_ip_set.access_ristrict : k => v.arn
for k, v in aws_wafv2_ip_set.access_ristrict:
- この部分で
aws_wafv2_ip_set.access_ristrict
で読み込まれる map の各要素に対してループ処理を行います。
- この部分で
k
は map のキー(トップレベルキー)、v
は map 内の値になります。- 繰り返し生成された IP Sets の ARN や ID は以下のような map で保持されています。キーは
"access-ristrict-sample2"
や"access-ristrict-sample2"
、map 内の値が"arn = "xxxxx"
やid = "xxxxx"
です。aws_wafv2_ip_set.access_ristrict = { "access-ristrict-sample2" = { arn = "xxxxx" id = "xxxxx" }, "access-ristrict-sample2" = { arn = "yyyyy" id = "xxxxx" }, }
- 繰り返し生成された IP Sets の ARN や ID は以下のような map で保持されています。キーは
: k => v.arn:
- この部分で新しい map の各エントリを定義しています。map のキー(k)はそのまま保持され、値は
v.arn
(IP Sets の ARN)に置き換えられます。 - 結果、以下のようなキーと IP Sets の ARN の新しい map が生成されます。
ip_set_arns = { "access-ristrict-sample2" = "xxxxx" "access-ristrict-sample3" = "xxxxx" }
- この部分で新しい map の各エントリを定義しています。map のキー(k)はそのまま保持され、値は
カスタムルールグループの output も同様です。
envs/dev-waf
backend.tf
backend.tf ファイルには Terraform の設定に関する情報を含む terraform
ブロックを記載しています。terraform
ブロックは Terraform を実行する際一番最初に読み込まれます。
envs/dev-waf/backend.tf(クリックで展開)
################################################## # Terraform settings ################################################## terraform { # https://developer.hashicorp.com/terraform/language/settings # Terraform バージョンの指定 required_version = "~> 1.5" # AWS プロバイダーのバージョン指定 https://registry.terraform.io/providers/hashicorp/aws/latest required_providers { aws = { source = "hashicorp/aws" version = "~> 5.1" } } # tfstate ファイルを S3 に配置する(配置先の S3 は事前に作成しておく) backend s3 { bucket = "<s3_bucket_name>" region = "ap-northeast-1" key = "<tfstate_file_name>.tfstate" } }
ここでは Terraform のバージョン、プロバイダーのバージョン、状態ファイル(tfstate ファイル)の保存場所を指定しています。
required_version
- 使用する Terraform のバージョンを指定します。
"~> 1.5"
は Terraform 1.5 系、1.6 系、1.7 系……のバージョンが利用できます。バージョン制約から外れる Terraform バージョンで実行しようとするとエラーとなり実行できません。
required_providers
ブロック- 使用するプロバイダーとバージョンを指定します。ここでは AWS を指定しています。
source
はプロバイダーの名前を指定します("hashicorp/aws"
)。version
は使用する AWS プロバイダーのバージョンを指定します("~> 5.1"
)。今回は 5.1 系、5.2 系、5.3 系……のバージョンを使用することを意味しています。
backend
ブロック- Terraform の状態ファイル(tfstate ファイル)をリモートで保存するための設定を定義します。今回はあらかじめ作成しておいた S3 バケットを指定しています。
region
は S3 バケットが存在するリージョンを指定します。key
は tfstate ファイルのパスとファイル名を指定します。
バージョン指定については以下ドキュメントを参照ください。
tfstate ファイルを S3 バケットで管理することで、複数メンバーが同じ Terraform コード管理でき、tfstate ファイルの損失や競合を防ぎます。
provider.tf
AWS で使用するリージョンを指定しています。
envs/dev-waf/provider.tf(クリックで展開)
################################################## # Provider settings ################################################## # AWS プロバイダーの定義 provider aws { region = "ap-northeast-1" }
main.tf
モジュールを呼び出して AWS WAF を実際にデプロイするファイルです。
envs/dev-waf/main.tf(クリックで展開)
############################################ # Common Values ############################################ module "common_values" { source = "../../shared/common_values" } ############################################ # WAF ############################################ module "waf" { source = "../../modules/waf" access_ristricted_paths_ips = module.common_values.access_ristricted_paths_ips # 動的生成ルールグループのための変数呼び出し # WAF を紐づける ALB の ARN alb_arn = "arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:loadbalancer/app/waf-test-alb/xxxxxxxxxxxxxxxx" }
module "common_values"
ブロックの呼び出しsource
引数を使用し../../shared/common_values
ディレクトリにあるcommon_values
モジュールを呼び出しています。
module "waf"
ブロックの呼び出しsource
引数を使用し../../modules/waf
ディレクトリにあるwaf
モジュールを呼び出しています。access_ristricted_paths_ips
引数common_values
モジュールから動的に生成されるWAFのルールグループで使用されるパスとIPアドレスのリストであるaccess_ristricted_paths_ips
の値を取得します。
alb_arn
引数- WAF を関連付ける ALB の ARN を指定しています。
使い方
0. terraform init、terraform plan、terraform apply をして Web ACL を作成する
まずは一度 terraform init、terraform plan、terraform apply をして Web ACL を作成しておきます。
1. Terraform コードの中で、名前、優先度、パス、IP の組み合わせを追記します。
shared/common_values/waf_rules.tf で access_ristricted_paths_ips = []
内に、以下のような名前、優先度、パス、IP の組み合わせのリストを追記します。
{ name = "access-ristrict-sample1" priority = 10 allow_path = "/ristrict/sample/" allow_ip_list = [ "0.0.0.0/1", "128.0.0.0/1", ] },
2. Terraform で更新
~/environment/test-terraform/envs/dev-waf
配下で更新をかけます。
terrafprm plan、apply(クリックで展開)
user.emi:~/environment/test-terraform/envs/dev-waf $ terraform plan module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample3"]: Refreshing state... [id=261098a7-82f6-49bd-bb9d-xxxxxxxxxx] module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample2"]: Refreshing state... [id=1461c7eb-52e6-42cc-97bb-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample2"]: Refreshing state... [id=0dd59d52-a04a-4740-b93a-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample3"]: Refreshing state... [id=0c82ad12-a05a-4af4-814d-xxxxxxxxxx] module.waf.aws_wafv2_web_acl.web_acl: Refreshing state... [id=xxxxxxxxxx] module.waf.aws_wafv2_web_acl_association.waf_association: Refreshing state... [id=arn:aws:wafv2:ap-northeast-1:123456789012:regional/webacl/alb-webacl/xxxxxxxxxx,arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:loadbalancer/app/waf-test-alb/xxxxxxxxxx] module.waf.aws_cloudwatch_log_group.cwlogs: Refreshing state... [id=aws-waf-logs-alb-webacl] module.waf.aws_wafv2_web_acl_logging_configuration.waflog: Refreshing state... [id=arn:aws:wafv2:ap-northeast-1:123456789012:regional/webacl/alb-webacl/xxxxxxxxxx] Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create ~ update in-place Terraform will perform the following actions: # module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample1"] will be created + resource "aws_wafv2_ip_set" "access_ristrict" { + addresses = [ + "0.0.0.0/1", + "128.0.0.0/1", ] + arn = (known after apply) + description = "access-ristrict-sample1-ristrict-ipsets" + id = (known after apply) + ip_address_version = "IPV4" + lock_token = (known after apply) + name = "access-ristrict-sample1-ristrict-ipsets" + scope = "REGIONAL" + tags = { + "Name" = "access-ristrict-sample1-ristrict-ipsets" } + tags_all = { + "Name" = "access-ristrict-sample1-ristrict-ipsets" } } # module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"] will be created + resource "aws_wafv2_rule_group" "access_ristrict" { + arn = (known after apply) + capacity = 3 + description = "access-ristrict-sample1-ristrict-waf-rulegp" + id = (known after apply) + lock_token = (known after apply) + name = "access-ristrict-sample1-ristrict-waf-rulegp" + name_prefix = (known after apply) + scope = "REGIONAL" + tags_all = (known after apply) + rule { + name = "access-ristrict-sample1-ristrict-rule" + priority = 1 + action { + block { } } + statement { + and_statement { + statement { + byte_match_statement { + positional_constraint = "STARTS_WITH" + search_string = "/ristrict/sample/" + field_to_match { + uri_path {} } + text_transformation { + priority = 0 + type = "NONE" } } } + statement { + not_statement { + statement { + ip_set_reference_statement { + arn = (known after apply) } } } } } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "access-ristrict-sample1-ristrict-rule" + sampled_requests_enabled = true } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "access-ristrict-sample1-ristrict-rulegp" + sampled_requests_enabled = true } } # module.waf.aws_wafv2_web_acl.web_acl will be updated in-place ~ resource "aws_wafv2_web_acl" "web_acl" { id = "xxxxxxxxxx" name = "alb-webacl" tags = { "Name" = "alb-webacl" } # (7 unchanged attributes hidden) + rule { + name = "Access-Ristrict-Sample1PathIpRestriction" + priority = 1010 + override_action { + none {} } + statement { + rule_group_reference_statement { + arn = (known after apply) } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "Access-Ristrict-Sample1PathIpRestriction" + sampled_requests_enabled = true } } # (4 unchanged blocks hidden) } Plan: 2 to add, 1 to change, 0 to destroy. ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now. user.emi:~/environment/test-terraform/envs/dev-waf $ user.emi:~/environment/test-terraform/envs/dev-waf $ terraform apply module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample2"]: Refreshing state... [id=1461c7eb-52e6-42cc-97bb-xxxxxxxxxx] module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample3"]: Refreshing state... [id=261098a7-82f6-49bd-bb9d-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample3"]: Refreshing state... [id=0c82ad12-a05a-4af4-814d-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample2"]: Refreshing state... [id=0dd59d52-a04a-4740-b93a-xxxxxxxxxx] module.waf.aws_wafv2_web_acl.web_acl: Refreshing state... [id=xxxxxxxxxx] module.waf.aws_wafv2_web_acl_association.waf_association: Refreshing state... [id=arn:aws:wafv2:ap-northeast-1:123456789012:regional/webacl/alb-webacl/xxxxxxxxxx,arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:loadbalancer/app/waf-test-alb/xxxxxxxxxx] module.waf.aws_cloudwatch_log_group.cwlogs: Refreshing state... [id=aws-waf-logs-alb-webacl] module.waf.aws_wafv2_web_acl_logging_configuration.waflog: Refreshing state... [id=arn:aws:wafv2:ap-northeast-1:123456789012:regional/webacl/alb-webacl/xxxxxxxxxx] Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create ~ update in-place Terraform will perform the following actions: # module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample1"] will be created + resource "aws_wafv2_ip_set" "access_ristrict" { + addresses = [ + "0.0.0.0/1", + "128.0.0.0/1", ] + arn = (known after apply) + description = "access-ristrict-sample1-ristrict-ipsets" + id = (known after apply) + ip_address_version = "IPV4" + lock_token = (known after apply) + name = "access-ristrict-sample1-ristrict-ipsets" + scope = "REGIONAL" + tags = { + "Name" = "access-ristrict-sample1-ristrict-ipsets" } + tags_all = { + "Name" = "access-ristrict-sample1-ristrict-ipsets" } } # module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"] will be created + resource "aws_wafv2_rule_group" "access_ristrict" { + arn = (known after apply) + capacity = 3 + description = "access-ristrict-sample1-ristrict-waf-rulegp" + id = (known after apply) + lock_token = (known after apply) + name = "access-ristrict-sample1-ristrict-waf-rulegp" + name_prefix = (known after apply) + scope = "REGIONAL" + tags_all = (known after apply) + rule { + name = "access-ristrict-sample1-ristrict-rule" + priority = 1 + action { + block { } } + statement { + and_statement { + statement { + byte_match_statement { + positional_constraint = "STARTS_WITH" + search_string = "/ristrict/sample/" + field_to_match { + uri_path {} } + text_transformation { + priority = 0 + type = "NONE" } } } + statement { + not_statement { + statement { + ip_set_reference_statement { + arn = (known after apply) } } } } } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "access-ristrict-sample1-ristrict-rule" + sampled_requests_enabled = true } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "access-ristrict-sample1-ristrict-rulegp" + sampled_requests_enabled = true } } # module.waf.aws_wafv2_web_acl.web_acl will be updated in-place ~ resource "aws_wafv2_web_acl" "web_acl" { id = "xxxxxxxxxx" name = "alb-webacl" tags = { "Name" = "alb-webacl" } # (7 unchanged attributes hidden) + rule { + name = "Access-Ristrict-Sample1PathIpRestriction" + priority = 1010 + override_action { + none {} } + statement { + rule_group_reference_statement { + arn = (known after apply) } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "Access-Ristrict-Sample1PathIpRestriction" + sampled_requests_enabled = true } } # (4 unchanged blocks hidden) } Plan: 2 to add, 1 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample1"]: Creating... module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample1"]: Creation complete after 0s [id=b18a8319-eaa3-4643-8853-74f1740d2af2] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Creating... module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Creation complete after 0s [id=befd7e4f-24de-4c68-9695-f3c80b9143ae] module.waf.aws_wafv2_web_acl.web_acl: Modifying... [id=xxxxxxxxxx] module.waf.aws_wafv2_web_acl.web_acl: Modifications complete after 2s [id=xxxxxxxxxx] Apply complete! Resources: 2 added, 1 changed, 0 destroyed. user.emi:~/environment/test-terraform/envs/dev-waf $
3. 指定した内容で IP Sets とルールグループを動的に生成される
以下のような IP Sets、ルールグループが作成されます。
{ "Name": "access-ristrict-sample1-ristrict-rule", "Priority": 1, "Statement": { "AndStatement": { "Statements": [ { "ByteMatchStatement": { "SearchString": "/ristrict/sample/", "FieldToMatch": { "UriPath": {} }, "TextTransformations": [ { "Priority": 0, "Type": "NONE" } ], "PositionalConstraint": "STARTS_WITH" } }, { "NotStatement": { "Statement": { "IPSetReferenceStatement": { "ARN": "arn:aws:wafv2:ap-northeast-1:123456789012:regional/ipset/access-ristrict-sample1-ristrict-ipsets/xxxxxxxxxx" } } } } ] } }, "Action": { "Block": {} }, "VisibilityConfig": { "SampledRequestsEnabled": true, "CloudWatchMetricsEnabled": true, "MetricName": "access-ristrict-sample1-ristrict-rule" } }
Web ACL には以下の名前でルールグループが紐づきます。
4. priority、path、IP を変更する
prioryty 変更
terraform plan、apply(クリックで展開)
cm-kitani.emi:~/environment/test-terraform/envs/dev-waf $ terraform plan module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample3"]: Refreshing state... [id=261098a7-82f6-49bd-bb9d-xxxxxxxxxx] module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample1"]: Refreshing state... [id=b18a8319-eaa3-4643-8853-xxxxxxxxxx] module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample2"]: Refreshing state... [id=1461c7eb-52e6-42cc-97bb-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample2"]: Refreshing state... [id=0dd59d52-a04a-4740-b93a-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Refreshing state... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample3"]: Refreshing state... [id=0c82ad12-a05a-4af4-814d-xxxxxxxxxx] module.waf.aws_wafv2_web_acl.web_acl: Refreshing state... [id=xxxxxxxxxx] module.waf.aws_wafv2_web_acl_association.waf_association: Refreshing state... [id=arn:aws:wafv2:ap-northeast-1:123456789012:regional/webacl/alb-webacl/xxxxxxxxxx,arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:loadbalancer/app/waf-test-alb/xxxxxxxxxx] module.waf.aws_cloudwatch_log_group.cwlogs: Refreshing state... [id=aws-waf-logs-alb-webacl] module.waf.aws_wafv2_web_acl_logging_configuration.waflog: Refreshing state... [id=arn:aws:wafv2:ap-northeast-1:123456789012:regional/webacl/alb-webacl/xxxxxxxxxx] Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: ~ update in-place Terraform will perform the following actions: # module.waf.aws_wafv2_web_acl.web_acl will be updated in-place ~ resource "aws_wafv2_web_acl" "web_acl" { id = "xxxxxxxxxx" name = "alb-webacl" tags = { "Name" = "alb-webacl" } # (7 unchanged attributes hidden) - rule { - name = "Access-Ristrict-Sample1PathIpRestriction" -> null - priority = 1010 -> null - override_action { - none {} } - statement { - rule_group_reference_statement { - arn = "arn:aws:wafv2:ap-northeast-1:123456789012:regional/rulegroup/access-ristrict-sample1-ristrict-waf-rulegp/befd7e4f-24de-4c68-9695-xxxxxxxxxx" -> null } } - visibility_config { - cloudwatch_metrics_enabled = true -> null - metric_name = "Access-Ristrict-Sample1PathIpRestriction" -> null - sampled_requests_enabled = true -> null } } + rule { + name = "Access-Ristrict-Sample1PathIpRestriction" + priority = 1005 + override_action { + none {} } + statement { + rule_group_reference_statement { + arn = "arn:aws:wafv2:ap-northeast-1:123456789012:regional/rulegroup/access-ristrict-sample1-ristrict-waf-rulegp/befd7e4f-24de-4c68-9695-xxxxxxxxxx" } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "Access-Ristrict-Sample1PathIpRestriction" + sampled_requests_enabled = true } } # (4 unchanged blocks hidden) } Plan: 0 to add, 1 to change, 0 to destroy. ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now. cm-kitani.emi:~/environment/test-terraform/envs/dev-waf $ terraform apply module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample3"]: Refreshing state... [id=261098a7-82f6-49bd-bb9d-xxxxxxxxxx] module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample1"]: Refreshing state... [id=b18a8319-eaa3-4643-8853-xxxxxxxxxx] module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample2"]: Refreshing state... [id=1461c7eb-52e6-42cc-97bb-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample2"]: Refreshing state... [id=0dd59d52-a04a-4740-b93a-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample3"]: Refreshing state... [id=0c82ad12-a05a-4af4-814d-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Refreshing state... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx] module.waf.aws_wafv2_web_acl.web_acl: Refreshing state... [id=xxxxxxxxxx] module.waf.aws_cloudwatch_log_group.cwlogs: Refreshing state... [id=aws-waf-logs-alb-webacl] module.waf.aws_wafv2_web_acl_association.waf_association: Refreshing state... [id=arn:aws:wafv2:ap-northeast-1:123456789012:regional/webacl/alb-webacl/xxxxxxxxxx,arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:loadbalancer/app/waf-test-alb/xxxxxxxxxx] module.waf.aws_wafv2_web_acl_logging_configuration.waflog: Refreshing state... [id=arn:aws:wafv2:ap-northeast-1:123456789012:regional/webacl/alb-webacl/xxxxxxxxxx] Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: ~ update in-place Terraform will perform the following actions: # module.waf.aws_wafv2_web_acl.web_acl will be updated in-place ~ resource "aws_wafv2_web_acl" "web_acl" { id = "xxxxxxxxxx" name = "alb-webacl" tags = { "Name" = "alb-webacl" } # (7 unchanged attributes hidden) - rule { - name = "Access-Ristrict-Sample1PathIpRestriction" -> null - priority = 1010 -> null - override_action { - none {} } - statement { - rule_group_reference_statement { - arn = "arn:aws:wafv2:ap-northeast-1:123456789012:regional/rulegroup/access-ristrict-sample1-ristrict-waf-rulegp/befd7e4f-24de-4c68-9695-xxxxxxxxxx" -> null } } - visibility_config { - cloudwatch_metrics_enabled = true -> null - metric_name = "Access-Ristrict-Sample1PathIpRestriction" -> null - sampled_requests_enabled = true -> null } } + rule { + name = "Access-Ristrict-Sample1PathIpRestriction" + priority = 1005 + override_action { + none {} } + statement { + rule_group_reference_statement { + arn = "arn:aws:wafv2:ap-northeast-1:123456789012:regional/rulegroup/access-ristrict-sample1-ristrict-waf-rulegp/befd7e4f-24de-4c68-9695-xxxxxxxxxx" } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "Access-Ristrict-Sample1PathIpRestriction" + sampled_requests_enabled = true } } # (4 unchanged blocks hidden) } Plan: 0 to add, 1 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes module.waf.aws_wafv2_web_acl.web_acl: Modifying... [id=xxxxxxxxxx] module.waf.aws_wafv2_web_acl.web_acl: Modifications complete after 2s [id=xxxxxxxxxx] Apply complete! Resources: 0 added, 1 changed, 0 destroyed. cm-kitani.emi:~/environment/test-terraform/envs/dev-waf $
path 変更
terraform plan、terraform apply(クリックで展開)
user.emi:~/environment/test-terraform/envs/dev-waf $ terraform plan module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample2"]: Refreshing state... [id=1461c7eb-52e6-42cc-97bb-xxxxxxxxxx] module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample1"]: Refreshing state... [id=b18a8319-eaa3-4643-8853-xxxxxxxxxx] module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample3"]: Refreshing state... [id=261098a7-82f6-49bd-bb9d-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample2"]: Refreshing state... [id=0dd59d52-a04a-4740-b93a-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Refreshing state... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample3"]: Refreshing state... [id=0c82ad12-a05a-4af4-814d-xxxxxxxxxx] module.waf.aws_wafv2_web_acl.web_acl: Refreshing state... [id=xxxxxxxxxx] module.waf.aws_wafv2_web_acl_association.waf_association: Refreshing state... [id=arn:aws:wafv2:ap-northeast-1:123456789012:regional/webacl/alb-webacl/xxxxxxxxxx,arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:loadbalancer/app/waf-test-alb/xxxxxxxxxx] module.waf.aws_cloudwatch_log_group.cwlogs: Refreshing state... [id=aws-waf-logs-alb-webacl] module.waf.aws_wafv2_web_acl_logging_configuration.waflog: Refreshing state... [id=arn:aws:wafv2:ap-northeast-1:123456789012:regional/webacl/alb-webacl/xxxxxxxxxx] Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: ~ update in-place Terraform will perform the following actions: # module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"] will be updated in-place ~ resource "aws_wafv2_rule_group" "access_ristrict" { id = "befd7e4f-24de-4c68-9695-xxxxxxxxxx" name = "access-ristrict-sample1-ristrict-waf-rulegp" tags = {} # (6 unchanged attributes hidden) - rule { - name = "access-ristrict-sample1-ristrict-rule" -> null - priority = 1 -> null - action { - block { } } - statement { - and_statement { - statement { - byte_match_statement { - positional_constraint = "STARTS_WITH" -> null - search_string = "/ristrict/sample/" -> null - field_to_match { - uri_path {} } - text_transformation { - priority = 0 -> null - type = "NONE" -> null } } } - statement { - not_statement { - statement { - ip_set_reference_statement { - arn = "arn:aws:wafv2:ap-northeast-1:123456789012:regional/ipset/access-ristrict-sample1-ristrict-ipsets/b18a8319-eaa3-4643-8853-xxxxxxxxxx" -> null } } } } } } - visibility_config { - cloudwatch_metrics_enabled = true -> null - metric_name = "access-ristrict-sample1-ristrict-rule" -> null - sampled_requests_enabled = true -> null } } + rule { + name = "access-ristrict-sample1-ristrict-rule" + priority = 1 + action { + block { } } + statement { + and_statement { + statement { + byte_match_statement { + positional_constraint = "STARTS_WITH" + search_string = "/ristrict/sample1/" + field_to_match { + uri_path {} } + text_transformation { + priority = 0 + type = "NONE" } } } + statement { + not_statement { + statement { + ip_set_reference_statement { + arn = "arn:aws:wafv2:ap-northeast-1:123456789012:regional/ipset/access-ristrict-sample1-ristrict-ipsets/b18a8319-eaa3-4643-8853-xxxxxxxxxx" } } } } } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "access-ristrict-sample1-ristrict-rule" + sampled_requests_enabled = true } } # (1 unchanged block hidden) } Plan: 0 to add, 1 to change, 0 to destroy. ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now. user.emi:~/environment/test-terraform/envs/dev-waf $ terraform apply module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample3"]: Refreshing state... [id=261098a7-82f6-49bd-bb9d-xxxxxxxxxx] module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample1"]: Refreshing state... [id=b18a8319-eaa3-4643-8853-xxxxxxxxxx] module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample2"]: Refreshing state... [id=1461c7eb-52e6-42cc-97bb-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample2"]: Refreshing state... [id=0dd59d52-a04a-4740-b93a-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample3"]: Refreshing state... [id=0c82ad12-a05a-4af4-814d-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Refreshing state... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx] module.waf.aws_wafv2_web_acl.web_acl: Refreshing state... [id=xxxxxxxxxx] module.waf.aws_cloudwatch_log_group.cwlogs: Refreshing state... [id=aws-waf-logs-alb-webacl] module.waf.aws_wafv2_web_acl_association.waf_association: Refreshing state... [id=arn:aws:wafv2:ap-northeast-1:123456789012:regional/webacl/alb-webacl/xxxxxxxxxx,arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:loadbalancer/app/waf-test-alb/xxxxxxxxxx] module.waf.aws_wafv2_web_acl_logging_configuration.waflog: Refreshing state... [id=arn:aws:wafv2:ap-northeast-1:123456789012:regional/webacl/alb-webacl/xxxxxxxxxx] Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: ~ update in-place Terraform will perform the following actions: # module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"] will be updated in-place ~ resource "aws_wafv2_rule_group" "access_ristrict" { id = "befd7e4f-24de-4c68-9695-xxxxxxxxxx" name = "access-ristrict-sample1-ristrict-waf-rulegp" tags = {} # (6 unchanged attributes hidden) - rule { - name = "access-ristrict-sample1-ristrict-rule" -> null - priority = 1 -> null - action { - block { } } - statement { - and_statement { - statement { - byte_match_statement { - positional_constraint = "STARTS_WITH" -> null - search_string = "/ristrict/sample/" -> null - field_to_match { - uri_path {} } - text_transformation { - priority = 0 -> null - type = "NONE" -> null } } } - statement { - not_statement { - statement { - ip_set_reference_statement { - arn = "arn:aws:wafv2:ap-northeast-1:123456789012:regional/ipset/access-ristrict-sample1-ristrict-ipsets/b18a8319-eaa3-4643-8853-xxxxxxxxxx" -> null } } } } } } - visibility_config { - cloudwatch_metrics_enabled = true -> null - metric_name = "access-ristrict-sample1-ristrict-rule" -> null - sampled_requests_enabled = true -> null } } + rule { + name = "access-ristrict-sample1-ristrict-rule" + priority = 1 + action { + block { } } + statement { + and_statement { + statement { + byte_match_statement { + positional_constraint = "STARTS_WITH" + search_string = "/ristrict/sample1/" + field_to_match { + uri_path {} } + text_transformation { + priority = 0 + type = "NONE" } } } + statement { + not_statement { + statement { + ip_set_reference_statement { + arn = "arn:aws:wafv2:ap-northeast-1:123456789012:regional/ipset/access-ristrict-sample1-ristrict-ipsets/b18a8319-eaa3-4643-8853-xxxxxxxxxx" } } } } } } + visibility_config { + cloudwatch_metrics_enabled = true + metric_name = "access-ristrict-sample1-ristrict-rule" + sampled_requests_enabled = true } } # (1 unchanged block hidden) } Plan: 0 to add, 1 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Modifying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Modifications complete after 1s [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx] Apply complete! Resources: 0 added, 1 changed, 0 destroyed. user.emi:~/environment/test-terraform/envs/dev-waf $
IP 変更
terraform plan、terraform apply(クリックで展開)
user.emi:~/environment/test-terraform/envs/dev-waf $ terraform plan module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample3"]: Refreshing state... [id=261098a7-82f6-49bd-xxxxxxxxxx] module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample2"]: Refreshing state... [id=1461c7eb-52e6-42cc-97bb-xxxxxxxxxx] module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample1"]: Refreshing state... [id=b18a8319-eaa3-4643-8853-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample2"]: Refreshing state... [id=0dd59d52-a04a-4740-b93a-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Refreshing state... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample3"]: Refreshing state... [id=0c82ad12-a05a-4af4-814d-xxxxxxxxxx] module.waf.aws_wafv2_web_acl.web_acl: Refreshing state... [id=xxxxxxxxxx] module.waf.aws_wafv2_web_acl_association.waf_association: Refreshing state... [id=arn:aws:wafv2:ap-northeast-1:123456789012:regional/webacl/alb-webacl/xxxxxxxxxx,arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:loadbalancer/app/waf-test-alb/xxxxxxxxxx] module.waf.aws_cloudwatch_log_group.cwlogs: Refreshing state... [id=aws-waf-logs-alb-webacl] module.waf.aws_wafv2_web_acl_logging_configuration.waflog: Refreshing state... [id=arn:aws:wafv2:ap-northeast-1:123456789012:regional/webacl/alb-webacl/xxxxxxxxxx] Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: ~ update in-place Terraform will perform the following actions: # module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample2"] will be updated in-place ~ resource "aws_wafv2_ip_set" "access_ristrict" { ~ addresses = [ - "128.0.0.0/1", # (1 unchanged element hidden) ] id = "1461c7eb-52e6-42cc-97bb-xxxxxxxxxx" name = "access-ristrict-sample2-ristrict-ipsets" tags = { "Name" = "access-ristrict-sample2-ristrict-ipsets" } # (6 unchanged attributes hidden) } Plan: 0 to add, 1 to change, 0 to destroy. ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now. user.emi:~/environment/test-terraform/envs/dev-waf $ terraform apply module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample1"]: Refreshing state... [id=b18a8319-eaa3-4643-8853-xxxxxxxxxx] module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample2"]: Refreshing state... [id=1461c7eb-52e6-42cc-97bb-xxxxxxxxxx] module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample3"]: Refreshing state... [id=261098a7-82f6-49bd-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Refreshing state... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample3"]: Refreshing state... [id=0c82ad12-a05a-4af4-814d-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample2"]: Refreshing state... [id=0dd59d52-a04a-4740-b93a-xxxxxxxxxx] module.waf.aws_wafv2_web_acl.web_acl: Refreshing state... [id=xxxxxxxxxx] module.waf.aws_wafv2_web_acl_association.waf_association: Refreshing state... [id=arn:aws:wafv2:ap-northeast-1:123456789012:regional/webacl/alb-webacl/xxxxxxxxxx,arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:loadbalancer/app/waf-test-alb/xxxxxxxxxx] module.waf.aws_cloudwatch_log_group.cwlogs: Refreshing state... [id=aws-waf-logs-alb-webacl] module.waf.aws_wafv2_web_acl_logging_configuration.waflog: Refreshing state... [id=arn:aws:wafv2:ap-northeast-1:123456789012:regional/webacl/alb-webacl/xxxxxxxxxx] Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: ~ update in-place Terraform will perform the following actions: # module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample2"] will be updated in-place ~ resource "aws_wafv2_ip_set" "access_ristrict" { ~ addresses = [ - "128.0.0.0/1", # (1 unchanged element hidden) ] id = "1461c7eb-52e6-42cc-97bb-xxxxxxxxxx" name = "access-ristrict-sample2-ristrict-ipsets" tags = { "Name" = "access-ristrict-sample2-ristrict-ipsets" } # (6 unchanged attributes hidden) } Plan: 0 to add, 1 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample2"]: Modifying... [id=1461c7eb-52e6-42cc-97bb-xxxxxxxxxx] module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample2"]: Modifications complete after 0s [id=1461c7eb-52e6-42cc-97bb-xxxxxxxxxx] Apply complete! Resources: 0 added, 1 changed, 0 destroyed. user.emi:~/environment/test-terraform/envs/dev-waf $
注意
shared/common_values/waf_rules.tf で access_ristricted_paths_ips = []
から以下のように名前、優先度、パス、IP の組み合わせのリストを削除して Terraform を更新するだけでは、ルールグループが Web ACL に紐づいている関係で削除が失敗します。
terraform apply(クリックで展開)
user.emi:~/environment/test-terraform/envs/dev-waf $ terraform apply module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample1"]: Refreshing state... [id=b18a8319-eaa3-4643-8853-xxxxxxxxxx] module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample3"]: Refreshing state... [id=261098a7-82f6-49bd-bb9d-xxxxxxxxxx] module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample2"]: Refreshing state... [id=1461c7eb-52e6-42cc-97bb-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample2"]: Refreshing state... [id=0dd59d52-a04a-4740-b93a-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Refreshing state... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample3"]: Refreshing state... [id=0c82ad12-a05a-4af4-814d-xxxxxxxxxx] module.waf.aws_wafv2_web_acl.web_acl: Refreshing state... [id=xxxxxxxxxx] module.waf.aws_cloudwatch_log_group.cwlogs: Refreshing state... [id=aws-waf-logs-alb-webacl] module.waf.aws_wafv2_web_acl_association.waf_association: Refreshing state... [id=arn:aws:wafv2:ap-northeast-1:123456789012:regional/webacl/alb-webacl/xxxxxxxxxx,arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:loadbalancer/app/waf-test-alb/xxxxxxxxxx] module.waf.aws_wafv2_web_acl_logging_configuration.waflog: Refreshing state... [id=arn:aws:wafv2:ap-northeast-1:123456789012:regional/webacl/alb-webacl/xxxxxxxxxx] Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: ~ update in-place - destroy Terraform will perform the following actions: # module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample1"] will be destroyed # (because key ["access-ristrict-sample1"] is not in for_each map) - resource "aws_wafv2_ip_set" "access_ristrict" { - addresses = [ - "0.0.0.0/1", - "128.0.0.0/1", ] -> null - arn = "arn:aws:wafv2:ap-northeast-1:123456789012:regional/ipset/access-ristrict-sample1-ristrict-ipsets/b18a8319-eaa3-4643-8853-xxxxxxxxxx" -> null - description = "access-ristrict-sample1-ristrict-ipsets" -> null - id = "b18a8319-eaa3-4643-8853-xxxxxxxxxx" -> null - ip_address_version = "IPV4" -> null - lock_token = "aede225d-0e76-471d-9030-06be5230c6b3" -> null - name = "access-ristrict-sample1-ristrict-ipsets" -> null - scope = "REGIONAL" -> null - tags = { - "Name" = "access-ristrict-sample1-ristrict-ipsets" } -> null - tags_all = { - "Name" = "access-ristrict-sample1-ristrict-ipsets" } -> null } # module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"] will be destroyed # (because key ["access-ristrict-sample1"] is not in for_each map) - resource "aws_wafv2_rule_group" "access_ristrict" { - arn = "arn:aws:wafv2:ap-northeast-1:123456789012:regional/rulegroup/access-ristrict-sample1-ristrict-waf-rulegp/befd7e4f-24de-4c68-9695-xxxxxxxxxx" -> null - capacity = 3 -> null - description = "access-ristrict-sample1-ristrict-waf-rulegp" -> null - id = "befd7e4f-24de-4c68-9695-xxxxxxxxxx" -> null - lock_token = "fb7a8d63-7dcf-4d2e-b650-3b8ef61e3eeb" -> null - name = "access-ristrict-sample1-ristrict-waf-rulegp" -> null - scope = "REGIONAL" -> null - tags = {} -> null - tags_all = {} -> null - rule { - name = "access-ristrict-sample1-ristrict-rule" -> null - priority = 1 -> null - action { - block { } } - statement { - and_statement { - statement { - byte_match_statement { - positional_constraint = "STARTS_WITH" -> null - search_string = "/ristrict/sample1/" -> null - field_to_match { - uri_path {} } - text_transformation { - priority = 0 -> null - type = "NONE" -> null } } } - statement { - not_statement { - statement { - ip_set_reference_statement { - arn = "arn:aws:wafv2:ap-northeast-1:123456789012:regional/ipset/access-ristrict-sample1-ristrict-ipsets/b18a8319-eaa3-4643-8853-xxxxxxxxxx" -> null } } } } } } - visibility_config { - cloudwatch_metrics_enabled = true -> null - metric_name = "access-ristrict-sample1-ristrict-rule" -> null - sampled_requests_enabled = true -> null } } - visibility_config { - cloudwatch_metrics_enabled = true -> null - metric_name = "access-ristrict-sample1-ristrict-rulegp" -> null - sampled_requests_enabled = true -> null } } # module.waf.aws_wafv2_web_acl.web_acl will be updated in-place ~ resource "aws_wafv2_web_acl" "web_acl" { id = "xxxxxxxxxx" name = "alb-webacl" tags = { "Name" = "alb-webacl" } # (7 unchanged attributes hidden) - rule { - name = "Access-Ristrict-Sample1PathIpRestriction" -> null - priority = 1010 -> null - override_action { - none {} } - statement { - rule_group_reference_statement { - arn = "arn:aws:wafv2:ap-northeast-1:123456789012:regional/rulegroup/access-ristrict-sample1-ristrict-waf-rulegp/befd7e4f-24de-4c68-9695-xxxxxxxxxx" -> null } } - visibility_config { - cloudwatch_metrics_enabled = true -> null - metric_name = "Access-Ristrict-Sample1PathIpRestriction" -> null - sampled_requests_enabled = true -> null } } # (4 unchanged blocks hidden) } Plan: 0 to add, 1 to change, 2 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Still destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx, 10s elapsed] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Still destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx, 20s elapsed] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Still destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx, 30s elapsed] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Still destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx, 40s elapsed] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Still destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx, 50s elapsed] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Still destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx, 1m0s elapsed] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Still destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx, 1m10s elapsed] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Still destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx, 1m20s elapsed] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Still destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx, 1m30s elapsed] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Still destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx, 1m40s elapsed] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Still destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx, 1m50s elapsed] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Still destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx, 2m0s elapsed] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Still destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx, 2m10s elapsed] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Still destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx, 2m20s elapsed] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Still destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx, 2m30s elapsed] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Still destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx, 2m40s elapsed] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Still destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx, 2m50s elapsed] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Still destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx, 3m0s elapsed] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Still destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx, 3m10s elapsed] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Still destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx, 3m20s elapsed] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Still destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx, 3m30s elapsed] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Still destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx, 3m40s elapsed] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Still destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx, 3m50s elapsed] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Still destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx, 4m0s elapsed] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Still destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx, 4m10s elapsed] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Still destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx, 4m20s elapsed] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Still destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx, 4m30s elapsed] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Still destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx, 4m40s elapsed] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Still destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx, 4m50s elapsed] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Still destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx, 5m0s elapsed] ╷ │ Error: deleting WAFv2 RuleGroup (befd7e4f-24de-4c68-9695-xxxxxxxxxx): WAFAssociatedItemException: AWS WAF couldn’t perform the operation because your resource is being used by another resource or it’s associated with another resource. │ │ ╵ user.emi:~/environment/test-terraform/envs/dev-waf $
エラー抜粋
╷ │ Error: deleting WAFv2 RuleGroup (befd7e4f-24de-4c68-9695-xxxxxxxxxx): WAFAssociatedItemException: AWS WAF couldn’t perform the operation because your resource is being used by another resource or it’s associated with another resource. │ │ ╵
追加したルールグループを削除したい場合、先に手動で Web ACL からルールグループを削除しておきます。
再度 Terraform を更新すると削除が完了します。
terraform plan、apply(クリックで展開)
user.emi:~/environment/test-terraform/envs/dev-waf $ terraform plan module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample2"]: Refreshing state... [id=1461c7eb-52e6-42cc-97bb-xxxxxxxxxx] module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample1"]: Refreshing state... [id=b18a8319-eaa3-4643-8853-xxxxxxxxxx] module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample3"]: Refreshing state... [id=261098a7-82f6-49bd-bb9d-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample2"]: Refreshing state... [id=0dd59d52-a04a-4740-b93a-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Refreshing state... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample3"]: Refreshing state... [id=0c82ad12-a05a-4af4-814d-xxxxxxxxxx] module.waf.aws_wafv2_web_acl.web_acl: Refreshing state... [id=xxxxxxxxxx] module.waf.aws_wafv2_web_acl_association.waf_association: Refreshing state... [id=arn:aws:wafv2:ap-northeast-1:123456789012:regional/webacl/alb-webacl/xxxxxxxxxx,arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:loadbalancer/app/waf-test-alb/xxxxxxxxxx] module.waf.aws_cloudwatch_log_group.cwlogs: Refreshing state... [id=aws-waf-logs-alb-webacl] module.waf.aws_wafv2_web_acl_logging_configuration.waflog: Refreshing state... [id=arn:aws:wafv2:ap-northeast-1:123456789012:regional/webacl/alb-webacl/xxxxxxxxxx] Note: Objects have changed outside of Terraform Terraform detected the following changes made outside of Terraform since the last "terraform apply" which may have affected this plan: # module.waf.aws_wafv2_web_acl.web_acl has changed ~ resource "aws_wafv2_web_acl" "web_acl" { ~ capacity = 9 -> 6 id = "xxxxxxxxxx" name = "alb-webacl" tags = { "Name" = "alb-webacl" } # (6 unchanged attributes hidden) # (5 unchanged blocks hidden) } Unless you have made equivalent changes to your configuration, or ignored the relevant attributes using ignore_changes, the following plan may include actions to undo or respond to these changes. ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: - destroy Terraform will perform the following actions: # module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample1"] will be destroyed # (because key ["access-ristrict-sample1"] is not in for_each map) - resource "aws_wafv2_ip_set" "access_ristrict" { - addresses = [ - "0.0.0.0/1", - "128.0.0.0/1", ] -> null - arn = "arn:aws:wafv2:ap-northeast-1:123456789012:regional/ipset/access-ristrict-sample1-ristrict-ipsets/b18a8319-eaa3-4643-8853-xxxxxxxxxx" -> null - description = "access-ristrict-sample1-ristrict-ipsets" -> null - id = "b18a8319-eaa3-4643-8853-xxxxxxxxxx" -> null - ip_address_version = "IPV4" -> null - lock_token = "aede225d-0e76-471d-9030-06be5230c6b3" -> null - name = "access-ristrict-sample1-ristrict-ipsets" -> null - scope = "REGIONAL" -> null - tags = { - "Name" = "access-ristrict-sample1-ristrict-ipsets" } -> null - tags_all = { - "Name" = "access-ristrict-sample1-ristrict-ipsets" } -> null } # module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"] will be destroyed # (because key ["access-ristrict-sample1"] is not in for_each map) - resource "aws_wafv2_rule_group" "access_ristrict" { - arn = "arn:aws:wafv2:ap-northeast-1:123456789012:regional/rulegroup/access-ristrict-sample1-ristrict-waf-rulegp/befd7e4f-24de-4c68-9695-xxxxxxxxxx" -> null - capacity = 3 -> null - description = "access-ristrict-sample1-ristrict-waf-rulegp" -> null - id = "befd7e4f-24de-4c68-9695-xxxxxxxxxx" -> null - lock_token = "fb7a8d63-7dcf-4d2e-b650-3b8ef61e3eeb" -> null - name = "access-ristrict-sample1-ristrict-waf-rulegp" -> null - scope = "REGIONAL" -> null - tags = {} -> null - tags_all = {} -> null - rule { - name = "access-ristrict-sample1-ristrict-rule" -> null - priority = 1 -> null - action { - block { } } - statement { - and_statement { - statement { - byte_match_statement { - positional_constraint = "STARTS_WITH" -> null - search_string = "/ristrict/sample1/" -> null - field_to_match { - uri_path {} } - text_transformation { - priority = 0 -> null - type = "NONE" -> null } } } - statement { - not_statement { - statement { - ip_set_reference_statement { - arn = "arn:aws:wafv2:ap-northeast-1:123456789012:regional/ipset/access-ristrict-sample1-ristrict-ipsets/b18a8319-eaa3-4643-8853-xxxxxxxxxx" -> null } } } } } } - visibility_config { - cloudwatch_metrics_enabled = true -> null - metric_name = "access-ristrict-sample1-ristrict-rule" -> null - sampled_requests_enabled = true -> null } } - visibility_config { - cloudwatch_metrics_enabled = true -> null - metric_name = "access-ristrict-sample1-ristrict-rulegp" -> null - sampled_requests_enabled = true -> null } } Plan: 0 to add, 0 to change, 2 to destroy. ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now. user.emi:~/environment/test-terraform/envs/dev-waf $ terraform apply module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample3"]: Refreshing state... [id=261098a7-82f6-49bd-bb9d-xxxxxxxxxx] module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample2"]: Refreshing state... [id=1461c7eb-52e6-42cc-97bb-xxxxxxxxxx] module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample1"]: Refreshing state... [id=b18a8319-eaa3-4643-8853-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample2"]: Refreshing state... [id=0dd59d52-a04a-4740-b93a-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample3"]: Refreshing state... [id=0c82ad12-a05a-4af4-814d-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Refreshing state... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx] module.waf.aws_wafv2_web_acl.web_acl: Refreshing state... [id=xxxxxxxxxx] module.waf.aws_wafv2_web_acl_association.waf_association: Refreshing state... [id=arn:aws:wafv2:ap-northeast-1:123456789012:regional/webacl/alb-webacl/xxxxxxxxxx,arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:loadbalancer/app/waf-test-alb/xxxxxxxxxx] module.waf.aws_cloudwatch_log_group.cwlogs: Refreshing state... [id=aws-waf-logs-alb-webacl] module.waf.aws_wafv2_web_acl_logging_configuration.waflog: Refreshing state... [id=arn:aws:wafv2:ap-northeast-1:123456789012:regional/webacl/alb-webacl/xxxxxxxxxx] Note: Objects have changed outside of Terraform Terraform detected the following changes made outside of Terraform since the last "terraform apply" which may have affected this plan: # module.waf.aws_wafv2_web_acl.web_acl has changed ~ resource "aws_wafv2_web_acl" "web_acl" { ~ capacity = 9 -> 6 id = "xxxxxxxxxx" name = "alb-webacl" tags = { "Name" = "alb-webacl" } # (6 unchanged attributes hidden) # (5 unchanged blocks hidden) } Unless you have made equivalent changes to your configuration, or ignored the relevant attributes using ignore_changes, the following plan may include actions to undo or respond to these changes. ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: - destroy Terraform will perform the following actions: # module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample1"] will be destroyed # (because key ["access-ristrict-sample1"] is not in for_each map) - resource "aws_wafv2_ip_set" "access_ristrict" { - addresses = [ - "0.0.0.0/1", - "128.0.0.0/1", ] -> null - arn = "arn:aws:wafv2:ap-northeast-1:123456789012:regional/ipset/access-ristrict-sample1-ristrict-ipsets/b18a8319-eaa3-4643-8853-xxxxxxxxxx" -> null - description = "access-ristrict-sample1-ristrict-ipsets" -> null - id = "b18a8319-eaa3-4643-8853-xxxxxxxxxx" -> null - ip_address_version = "IPV4" -> null - lock_token = "aede225d-0e76-471d-9030-06be5230c6b3" -> null - name = "access-ristrict-sample1-ristrict-ipsets" -> null - scope = "REGIONAL" -> null - tags = { - "Name" = "access-ristrict-sample1-ristrict-ipsets" } -> null - tags_all = { - "Name" = "access-ristrict-sample1-ristrict-ipsets" } -> null } # module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"] will be destroyed # (because key ["access-ristrict-sample1"] is not in for_each map) - resource "aws_wafv2_rule_group" "access_ristrict" { - arn = "arn:aws:wafv2:ap-northeast-1:123456789012:regional/rulegroup/access-ristrict-sample1-ristrict-waf-rulegp/befd7e4f-24de-4c68-9695-xxxxxxxxxx" -> null - capacity = 3 -> null - description = "access-ristrict-sample1-ristrict-waf-rulegp" -> null - id = "befd7e4f-24de-4c68-9695-xxxxxxxxxx" -> null - lock_token = "fb7a8d63-7dcf-4d2e-b650-3b8ef61e3eeb" -> null - name = "access-ristrict-sample1-ristrict-waf-rulegp" -> null - scope = "REGIONAL" -> null - tags = {} -> null - tags_all = {} -> null - rule { - name = "access-ristrict-sample1-ristrict-rule" -> null - priority = 1 -> null - action { - block { } } - statement { - and_statement { - statement { - byte_match_statement { - positional_constraint = "STARTS_WITH" -> null - search_string = "/ristrict/sample1/" -> null - field_to_match { - uri_path {} } - text_transformation { - priority = 0 -> null - type = "NONE" -> null } } } - statement { - not_statement { - statement { - ip_set_reference_statement { - arn = "arn:aws:wafv2:ap-northeast-1:123456789012:regional/ipset/access-ristrict-sample1-ristrict-ipsets/b18a8319-eaa3-4643-8853-xxxxxxxxxx" -> null } } } } } } - visibility_config { - cloudwatch_metrics_enabled = true -> null - metric_name = "access-ristrict-sample1-ristrict-rule" -> null - sampled_requests_enabled = true -> null } } - visibility_config { - cloudwatch_metrics_enabled = true -> null - metric_name = "access-ristrict-sample1-ristrict-rulegp" -> null - sampled_requests_enabled = true -> null } } Plan: 0 to add, 0 to change, 2 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Destroying... [id=befd7e4f-24de-4c68-9695-xxxxxxxxxx] module.waf.aws_wafv2_rule_group.access_ristrict["access-ristrict-sample1"]: Destruction complete after 1s module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample1"]: Destroying... [id=b18a8319-eaa3-4643-8853-xxxxxxxxxx] module.waf.aws_wafv2_ip_set.access_ristrict["access-ristrict-sample1"]: Destruction complete after 0s Apply complete! Resources: 0 added, 0 changed, 2 destroyed. user.emi:~/environment/test-terraform/envs/dev-waf $
おわりに
ALB に紐づける AWS WAF のルールグループを動的に生成する Terraform コードを作成しました。
どなたかのお役に立てば幸いです。ご意見もお待ちしております。